﻿using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CdhpyScheduler.Core
{
    public class ScheduleCenter
    {
        private List<JobModel> scheduledjobs = new List<JobModel>();
        private IScheduler scheduler;

        /// <summary>
        /// Constructor
        /// </summary>
        public ScheduleCenter()
        {
            // Grab the Scheduler instance from the Factory 
            scheduler = StdSchedulerFactory.GetDefaultScheduler();
        }

        /// <summary>
        /// Get all created and scheduled jobs
        /// </summary>
        /// <returns>jobs list deep cloned</returns>
        public List<JobModel> GetAllScheduledJobs()
        {
            List<JobModel> jobs = new List<JobModel>();
            foreach (var job in scheduledjobs)
            {
                jobs.Add((JobModel)job.Clone());
            }
            return scheduledjobs;
        }

        /// <summary>
        /// Get current running scheduled jobs
        /// </summary>
        /// <returns>jobs list deep cloned</returns>
        public List<JobModel> GetRunningScheduledJobs()
        {
            List<JobModel> jobs = new List<JobModel>();
            foreach (var job in scheduledjobs)
            {
                if (CheckJobExist(job.jobKey_name, job.jobKey_group))
                    jobs.Add((JobModel)job.Clone());
            }
            return scheduledjobs;
        }

        /// <summary>
        /// Check if the job passed has already scheduled 
        /// </summary>
        /// <param name="target"></param>
        /// <returns>true: has duplicated and scheduled job</returns>
        public bool CheckJobDuplication(JobModel target)
        {
            Predicate<JobModel> predicate = (JobModel j) => { return j.jobKey_name == target.jobKey_name && j.jobKey_group == target.jobKey_group; };

            if (scheduledjobs.Exists(predicate))
            {
                Func<JobModel, bool> funcPredicate = new Func<JobModel, bool>(predicate);
                var item = scheduledjobs.First(funcPredicate);
                if (item.Equals(target))
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Check if job scheduled
        /// </summary>
        /// <returns></returns>
        public bool CheckJobExist(string jobKeyName, string groupName)
        {
            JobKey jk = new JobKey(jobKeyName, groupName);
            return CheckJobExist(jk);
        }

        /// <summary>
        /// Check if job scheduled
        /// </summary>
        /// <returns></returns>
        public bool CheckJobExist(JobKey jk)
        {
            return scheduler.CheckExists(jk);
        }

        /// <summary>
        /// Get job's trigger time by name and group
        /// </summary>
        /// <param name="triggerName"></param>
        /// <returns>key: last trigger, value: next trigger</returns>
        public KeyValuePair<DateTime?, DateTime?> GetTriggerTime(string triggerName, string jobKeyGroupName)
        {
            DateTime? last = null;
            DateTime? next = null;
            var trigger = scheduler.GetTrigger(new TriggerKey(triggerName, jobKeyGroupName));
            if (trigger != null)
            {
                last = trigger.GetPreviousFireTimeUtc()?.AddHours(8).DateTime;
                next = trigger.GetNextFireTimeUtc()?.AddHours(8).DateTime;
            }
            return new KeyValuePair<DateTime?, DateTime?>(last, next);
        }

        /// <summary>
        /// Start scheduler off
        /// </summary>
        public void Start()
        {
            scheduler.Start();
        }

        /// <summary>
        /// Shut down the scheduler
        /// </summary>
        public void Shutdown()
        {
            scheduler.Shutdown();
        }

        /// <summary>
        /// Pause all jobs
        /// </summary>
        public void PauseAll()
        {
            scheduler.PauseAll();
        }

        /// <summary>
        /// Resume all jobs
        /// </summary>
        public void ResumeAll()
        {
            scheduler.ResumeAll();
        }

        /// <summary>
        /// Check if job exists. If yse, delete the identified job from the Scheduler and any associated triggers.
        /// </summary>
        /// <returns></returns>
        public void TryRemoveJob(string jobKeyName, string groupName)
        {
            var jk = new JobKey(jobKeyName, groupName);
            TryRemoveJob(jk);
        }

        /// <summary>
        /// Check if job exists. If yse, delete the identified job from the Scheduler and any associated triggers.
        /// </summary>
        /// <param name="jk"></param>
        public void TryRemoveJob(JobKey jk)
        {
            if (CheckJobExist(jk))
            {
                scheduler.DeleteJob(jk);
            }
            if (scheduledjobs.Exists(x => x.jobKey_name == jk.Name))
            {
                scheduledjobs.Remove(scheduledjobs.First(x => x.jobKey_name == jk.Name));
            }
        }

        /// <summary>
        /// Create job
        /// </summary>
        /// <param name="_job">job to be created</param>
        public void Create(JobModel _job)
        {
            JobKey jk = new JobKey(_job.jobKey_name, _job.jobKey_group);
            if (scheduledjobs?.Count > 0 == false)
            {
                scheduledjobs = new List<JobModel>();
            }
            else
            {
                TryRemoveJob(jk);
            }

            ScheduleJob(_job, jk);
            scheduledjobs.Add(_job);
        }

        /// <summary>
        /// Create jobs
        /// </summary>
        /// <param name="_jobs">jobs to be created</param>
        public void Create(List<JobModel> _jobs)
        {
            if (scheduledjobs?.Count > 0 == false)
            {
                scheduledjobs = new List<JobModel>();
            }

            foreach (var sj in _jobs)
            {
                JobKey jk = new JobKey(sj.jobKey_name, sj.jobKey_group);
                TryRemoveJob(jk);
                ScheduleJob(sj, jk);
                scheduledjobs.Add(sj);
            }
        }

        /// <summary>
        /// Add the job to the Scheduler
        /// </summary>
        /// <param name="sj">job entity item</param>
        /// <param name="jk">the job key</param>
        private void ScheduleJob(JobModel sj, JobKey jk = null)
        {
            if (jk == null)
                jk = new JobKey(sj.jobKey_name, sj.jobKey_group);

            if (sj.interval_days == 0 && sj.interval_hours == 0 && sj.interval_minutes == 0 && sj.interval_seconds == 0)
                return;

            //build job
            IJobDetail job = JobBuilder.Create<HttpJob>()
                    .WithIdentity(jk)
                    .UsingJobData("url", sj.url)
                    .UsingJobData("httpMethod", sj.http_method ?? "GET")
                    .Build();

            //build trigger
            var triggerBuilder = TriggerBuilder.Create()
                .WithIdentity(sj.trigger_name, sj.jobKey_group);
            if (sj.interval_type == JobModel_IntervalType.每天)
            {
                //learn more "CronTrigger" on https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/crontriggers.html
                string cronExp = $"{sj.interval_seconds} {sj.interval_minutes} {sj.interval_hours} * * ?";
                triggerBuilder.WithCronSchedule(cronExp);
            }
            else if (sj.interval_type == JobModel_IntervalType.每间隔一段时间)
            {
                triggerBuilder.WithSimpleSchedule(x =>
                {
                    x = x.WithIntervalInSeconds(sj.interval);
                    if (sj.repeate_count > 0)
                        x = x.WithRepeatCount(sj.repeate_count);
                    else
                        x = x.RepeatForever();
                });
            }
            else
            {
                return;
            }
            ITrigger trigger = triggerBuilder.Build();

            //schedule the job
            scheduler.ScheduleJob(job, trigger);
        }

        /// <summary>
        /// schedule temp job
        /// </summary>
        /// <param name="sj"></param>
        /// <param name="dt">first fire datetime</param>
        /// <param name="jk"></param>
        private void ScheduleJob_Temp(JobModel sj, DateTimeOffset dt, JobKey jk = null)
        {
            if (jk == null)
                jk = new JobKey(sj.jobKey_name, sj.jobKey_group);

            //build job
            IJobDetail job = JobBuilder.Create<HttpJob>()
                    .WithIdentity(jk)
                    .UsingJobData("url", sj.url)
                    .UsingJobData("httpMethod", sj.http_method ?? "GET")
                    .Build();

            //build trigger
            var triggerBuilder = TriggerBuilder.Create()
                .WithIdentity(sj.trigger_name, sj.jobKey_group);
            if (sj.interval_type == JobModel_IntervalType.每间隔一段时间)
            {
                triggerBuilder.StartAt(dt).WithSimpleSchedule(x =>
                {
                    x = x.WithIntervalInSeconds(sj.interval);
                    x = x.WithRepeatCount(0); //only first once
                });
            }
            else
            {
                return;
            }
            ITrigger trigger = triggerBuilder.Build();

            //schedule the job
            scheduler.ScheduleJob(job, trigger);
        }

        /// <summary>
        /// Create temp jobs
        /// </summary>
        /// <param name="_job"></param>
        /// <param name="dt"></param>
        public void Create_Temp(JobModel _job, DateTimeOffset dt)
        {
            JobKey jk = new JobKey(_job.jobKey_name, _job.jobKey_group);
            if (scheduledjobs?.Count > 0 == false)
            {
                scheduledjobs = new List<JobModel>();
            }
            else
            {
                TryRemoveJob(jk);
            }

            ScheduleJob_Temp(_job, dt, jk);
            scheduledjobs.Add(_job);
        }
    }
}
